// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/child/shared_memory_received_data_factory.h"

#include <stddef.h>
#include <tuple>

#include "content/common/resource_messages.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_sender.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

    using ::testing::_;
    using ::testing::InSequence;
    using ::testing::MockFunction;
    using ::testing::Return;
    using ::testing::StrictMock;

    using Checkpoint = StrictMock<MockFunction<void(int)>>;
    using ReceivedData = RequestPeer::ReceivedData;

    class MockSender : public IPC::Sender {
    public:
        bool Send(IPC::Message* message) override
        {
            bool result = false;
            if (message->type() == ResourceHostMsg_DataReceived_ACK::ID) {
                std::tuple<int> args;
                ResourceHostMsg_DataReceived_ACK::Read(message, &args);
                result = SendAck(std::get<0>(args));
            } else {
                result = SendOtherwise(message);
            }
            delete message;
            return result;
        }
        MOCK_METHOD1(SendAck, bool(int));
        MOCK_METHOD1(SendOtherwise, bool(IPC::Message*));
    };

    class SharedMemoryReceivedDataFactoryTest : public ::testing::Test {
    protected:
        void SetUp() override
        {
            sender_.reset(new StrictMock<MockSender>);
            request_id_ = 0xdeadbeaf;
            memory_.reset(new base::SharedMemory);
            factory_ = make_scoped_refptr(new SharedMemoryReceivedDataFactory(
                sender_.get(), request_id_, memory_));
            ASSERT_TRUE(memory_->CreateAndMapAnonymous(memory_size));

            ON_CALL(*sender_, SendAck(_)).WillByDefault(Return(true));
            ON_CALL(*sender_, SendOtherwise(_)).WillByDefault(Return(true));
        }

        static const size_t memory_size = 4 * 1024;
        std::unique_ptr<MockSender> sender_;
        int request_id_;
        linked_ptr<base::SharedMemory> memory_;
        scoped_refptr<SharedMemoryReceivedDataFactory> factory_;
    };

    TEST_F(SharedMemoryReceivedDataFactoryTest, Create)
    {
        Checkpoint checkpoint;
        InSequence s;
        EXPECT_CALL(checkpoint, Call(0));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(1));

        std::unique_ptr<ReceivedData> data = factory_->Create(12, 34);
        const char* memory_start = static_cast<const char*>(memory_->memory());

        ASSERT_TRUE(data);
        EXPECT_EQ(memory_start + 12, data->payload());
        EXPECT_EQ(34, data->length());

        checkpoint.Call(0);
        data.reset();
        checkpoint.Call(1);
    }

    TEST_F(SharedMemoryReceivedDataFactoryTest, CreateMultiple)
    {
        Checkpoint checkpoint;
        InSequence s;
        EXPECT_CALL(checkpoint, Call(0));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(1));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(2));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(3));

        std::unique_ptr<ReceivedData> data1 = factory_->Create(0, 1);
        std::unique_ptr<ReceivedData> data2 = factory_->Create(1, 1);
        std::unique_ptr<ReceivedData> data3 = factory_->Create(2, 1);

        EXPECT_TRUE(data1);
        EXPECT_TRUE(data2);
        EXPECT_TRUE(data3);

        checkpoint.Call(0);
        data1.reset();
        checkpoint.Call(1);
        data2.reset();
        checkpoint.Call(2);
        data3.reset();
        checkpoint.Call(3);
    }

    TEST_F(SharedMemoryReceivedDataFactoryTest, ReclaimOutOfOrder)
    {
        Checkpoint checkpoint;
        InSequence s;
        EXPECT_CALL(checkpoint, Call(0));
        EXPECT_CALL(checkpoint, Call(1));
        EXPECT_CALL(checkpoint, Call(2));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(3));

        std::unique_ptr<ReceivedData> data1 = factory_->Create(0, 1);
        std::unique_ptr<ReceivedData> data2 = factory_->Create(1, 1);
        std::unique_ptr<ReceivedData> data3 = factory_->Create(2, 1);

        EXPECT_TRUE(data1);
        EXPECT_TRUE(data2);
        EXPECT_TRUE(data3);

        checkpoint.Call(0);
        data3.reset();
        checkpoint.Call(1);
        data2.reset();
        checkpoint.Call(2);
        data1.reset();
        checkpoint.Call(3);
    }

    TEST_F(SharedMemoryReceivedDataFactoryTest, ReclaimOutOfOrderPartially)
    {
        Checkpoint checkpoint;
        InSequence s;
        EXPECT_CALL(checkpoint, Call(0));
        EXPECT_CALL(checkpoint, Call(1));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(2));
        EXPECT_CALL(checkpoint, Call(3));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(4));

        std::unique_ptr<ReceivedData> data1 = factory_->Create(0, 1);
        std::unique_ptr<ReceivedData> data2 = factory_->Create(1, 1);
        std::unique_ptr<ReceivedData> data3 = factory_->Create(2, 1);
        std::unique_ptr<ReceivedData> data4 = factory_->Create(3, 1);
        std::unique_ptr<ReceivedData> data5 = factory_->Create(4, 1);
        std::unique_ptr<ReceivedData> data6 = factory_->Create(5, 1);

        EXPECT_TRUE(data1);
        EXPECT_TRUE(data2);
        EXPECT_TRUE(data3);
        EXPECT_TRUE(data4);
        EXPECT_TRUE(data5);
        EXPECT_TRUE(data6);

        checkpoint.Call(0);
        data3.reset();
        data6.reset();
        data2.reset();
        checkpoint.Call(1);
        data1.reset();
        checkpoint.Call(2);
        data5.reset();
        checkpoint.Call(3);
        data4.reset();
        checkpoint.Call(4);
    }

    TEST_F(SharedMemoryReceivedDataFactoryTest, Stop)
    {
        Checkpoint checkpoint;
        InSequence s;
        EXPECT_CALL(checkpoint, Call(0));
        EXPECT_CALL(checkpoint, Call(1));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(*sender_, SendAck(request_id_));
        EXPECT_CALL(checkpoint, Call(2));
        EXPECT_CALL(checkpoint, Call(3));

        std::unique_ptr<ReceivedData> data1 = factory_->Create(0, 1);
        std::unique_ptr<ReceivedData> data2 = factory_->Create(1, 1);
        std::unique_ptr<ReceivedData> data3 = factory_->Create(2, 1);
        std::unique_ptr<ReceivedData> data4 = factory_->Create(3, 1);
        std::unique_ptr<ReceivedData> data5 = factory_->Create(4, 1);
        std::unique_ptr<ReceivedData> data6 = factory_->Create(5, 1);

        EXPECT_TRUE(data1);
        EXPECT_TRUE(data2);
        EXPECT_TRUE(data3);
        EXPECT_TRUE(data4);
        EXPECT_TRUE(data5);
        EXPECT_TRUE(data6);

        checkpoint.Call(0);
        data3.reset();
        data6.reset();
        data2.reset();
        checkpoint.Call(1);
        data1.reset();
        checkpoint.Call(2);
        factory_->Stop();
        data5.reset();
        data4.reset();
        checkpoint.Call(3);
    }

} // namespace

} // namespace content
